﻿using System;
using System.Diagnostics;
using System.Linq;
using RedirectManager.Interfaces;
using Sitecore;
using Sitecore.Diagnostics;
using Sitecore.Exceptions;
using Sitecore.Pipelines.HttpRequest;
using Sitecore.Reflection;

using Convert = Sitecore.Convert;

namespace RedirectManager.Pipelines.HttpRequest
{
    public class Redirector : HttpRequestProcessor
    {
        private static ILookupProvider provider;

        static Redirector()
        {
            // Debugger.Launch(); // Hard to get to this breakpoint without this as it initializes so early.
            Initialize();
        }

        private static void Initialize()
        {
            provider = (ILookupProvider)ReflectionUtil.CreateObjectFromConfig("dataproviders/redirectmanager");
            if (provider == null)
            {
                throw new InvalidTypeException("Redirect Manager: failed to create LookupProvider from config");
            }
        }

        #region Fields

        public static ILookupProvider Provider
        {
            get
            {
                return provider;
            }
        }

        #endregion

        #region Methods

        public override void Process(HttpRequestArgs args)
        {
            var stopWatch = new Stopwatch();
            stopWatch.Start();
   
            // Check provider
            if (Provider == null)
            {
                return;
            }

            // Check context
            if (Context.Site == null || Context.Database == null)
            {
                return;
            }

            // Check ignored sites
            if (Config.IgnoredSites.Contains(Context.Site.Name, StringComparer.OrdinalIgnoreCase))
            {
                return;
            }

            // Check Sitecore page mode
            if (IsIgnoredPageMode())
            {
                return;
            }

            // A resolving context item takes priorty over a redirects. Also prevents redirect loops.
            if (Context.Item != null)
            {
                return;
            }

            // Somehow invoked out of context?
            if (args == null || args.Context == null || args.Context.Request == null || args.Context.Request.Path == null)
            {
                return;
            }

            // Lookup request url, fetch redirect record from provider if one exists.
            IRedirect redirect = Provider.LookupUrl(args.Context.Request.Path);
            if (redirect == null)
            {
                // No redirect found.
                return;
            }

            // Check redirect is enabled.
            if (!redirect.Enabled)
            {
                return;
            }

            // Check valid site context
            if (Config.SiteContextChecking && redirect.Sites != null && !redirect.Sites.Contains(Context.Site.Name, StringComparer.OrdinalIgnoreCase))
            {
                return;
            }

            // Get the url
            var url = redirect.GetTargetUrl(Context.Site.Name);

            if (!string.IsNullOrEmpty(url))
            {
                // Log timings
                if (Config.LogProcessorStopwatch)
                {
                    stopWatch.Stop();
                    double ms = ((double)stopWatch.ElapsedTicks / (double)Stopwatch.Frequency) * 1000; // ms
                    Log.Info(string.Format("Redirect Manager: httprequest processor total time is : {0}ms ({1} ticks)", ms.ToString("F"), stopWatch.ElapsedTicks), this);
                }
                
                // Send the http response
                Respond(args, redirect.ResponseStatusCode, GetResponseStatusDescription(redirect.ResponseStatusCode), url);
            }
        }

        private static string GetResponseStatusDescription(int statusCode)
        {
            var description = string.Empty;

            if (statusCode == 301)
            {
                description = Config.StatusDescription301;
            }
            else if (statusCode == 302)
            {
                description = Config.StatusDescription302;
            }

            return description;
        }


        private static bool IsIgnoredPageMode()
        {
            if (!Context.PageMode.IsNormal)
            {
                return true;
            }

            if (Context.PageMode.IsPageEditor || Context.PageMode.IsPreview || Context.PageMode.IsPageEditorEditing)
            {
                return true;
            }

            return false;
        }

        private static void Respond(HttpRequestArgs args, int responseStatusCode, string responseStatusDescription, string responseUrl)
        {
            args.Context.Response.Clear();
            args.Context.Response.StatusCode = responseStatusCode;
            args.Context.Response.StatusDescription = responseStatusDescription;
            args.Context.Response.RedirectLocation = responseUrl;
            args.Context.Response.Write(string.Format("<html><head>\n<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<title>{0} Moved</title></head>\n", responseStatusCode));
            args.Context.Response.Write(string.Format("<body><h1>{0} Moved</h1>\nThe document has moved <a href=\"{1}\">here</a>.</body></html>", responseStatusCode, responseUrl));
            args.Context.Response.End();
        }

        #endregion
    }
}
